﻿using FastDFSCore.Protocols;
using FileStorageCenter.Services.FileStorageProvider;
using FileStorageCenter.Services.FileStorageProvider.FastDFS;
using FileStorageCenter.Services.Models;
using HelenServer.Modules.FileStorage.Abstractions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Diagnostics;
using System.IO.Hashing;
using FileInfo = FileStorageCenter.Services.Models.FileInfo;

namespace FileStorageCenter.Services.Services.Impl;

public class FileStorageService : IFileStorageService
{
    private readonly DbContext _context;
    private readonly IServiceProvider _provider;
    private readonly XxHash64 _xxh64;
    private readonly FastDFSContainerOptions _options;

    public FileStorageService(
        DbContext context,
        IServiceProvider provider,
        IOptions<FastDFSContainerOptions> options)
    {
        _context = context;
        _provider = provider;
        _xxh64 = new XxHash64();
        _options = options.Value;
    }

    public async ValueTask<OperationalResult<DownloadModel>> DownloadFileAsync(string fileid, CancellationToken cancellationToken = default)
    {
        var result = new OperationalResult<DownloadModel>();
        var fs = GetProvider();

        var entity = await _context.Set<FileInfo>().FirstOrDefaultAsync(n => n.FileId == fileid, cancellationToken);

        if (entity is not null)
        {
            var buffer = await fs.DownloadFileAsync(_options.GroupName, fileid, _options.ClusterName);

            result.Data = new DownloadModel { Id = entity.Id, FileId = entity.FileId, Name = entity.Name, Buffer = buffer };

            return result;
        }

        result.Status = OperationalResult.FAILURE;
        result.Message = "未找到文件存储元信息";

        return result;
    }

    public async ValueTask<OperationalResult<FastDFSFileInfo>> GetFileInfo(string fileid)
    {
        var result = new OperationalResult<FastDFSFileInfo>();

        var fs = GetProvider();

        var entity = await _context.Set<FileInfo>().FirstOrDefaultAsync(n => n.FileId == fileid);

        if (entity is not null)
        {
            result.Data = await fs.GetFileInfo(_options.GroupName, fileid, _options.ClusterName);

            return result;
        }

        result.Status = OperationalResult.FAILURE;
        result.Message = "未找到文件存储元信息";

        return result;
    }

    public async ValueTask<OperationalResult<StorageNode>> GetStorageNodeAsync(string fileid)
    {
        var result = new OperationalResult<StorageNode>();

        var fs = GetProvider();

        result.Data = await fs.GetStorageNodeAsync(_options.GroupName, fileid, _options.ClusterName);

        return result;
    }

    public async ValueTask<OperationalResult<string>> GetTokenAsync(string fileid, DateTime? dateTime = null)
    {
        var result = new OperationalResult<string>();

        var fs = GetProvider();

        var entity = await _context.Set<FileInfo>().FirstOrDefaultAsync(n => n.FileId == fileid);

        if (entity is not null)
        {
            result.Data = fs.GetToken(fileid, _options.ClusterName, dateTime);

            return result;
        }

        result.Status = OperationalResult.FAILURE;
        result.Message = "未找到文件存储元信息";

        return result;
    }

    public async ValueTask<IReadOnlyCollection<GroupInfo>> ListAllGroupInfosAsync()
    {
        var fs = GetProvider();

        return await fs.ListAllGroupInfosAsync(_options.ClusterName);
    }

    public async ValueTask<OperationalResult<GroupInfo>> ListOneGroupInfoAsync()
    {
        var result = new OperationalResult<GroupInfo>();

        var fs = GetProvider();

        result.Data = await fs.ListOneGroupInfoAsync(_options.GroupName, _options.ClusterName);

        return result;
    }

    public async ValueTask<IReadOnlyCollection<StorageInfo>> ListStorageInfosAsync()
    {
        var fs = GetProvider();

        return await fs.ListStorageInfosAsync(_options.GroupName, _options.ClusterName);
    }

    public async ValueTask<OperationalResult<StorageNode>> QueryStorageNodeForFileAsync(string fileid)
    {
        var result = new OperationalResult<StorageNode>();

        var fs = GetProvider();

        var entity = await _context.Set<FileInfo>().FirstOrDefaultAsync(n => n.FileId == fileid);

        if (entity is not null)
        {
            result.Data = await fs.QueryStorageNodeForFileAsync(_options.GroupName, fileid, _options.ClusterName);

            return result;
        }

        result.Status = OperationalResult.FAILURE;
        result.Message = "未找到文件存储元信息";

        return result;
    }

    public async ValueTask<IReadOnlyCollection<StorageNode>> QueryStorageNodesForFileAsync(string fileid)
    {
        var fs = GetProvider();

        var entity = await _context.Set<FileInfo>().FirstOrDefaultAsync(n => n.FileId == fileid);

        return entity is not null
            ? await fs.QueryStorageNodesForFileAsync(_options.GroupName, fileid, _options.ClusterName)
            : throw new InvalidOperationException("不存在对于的文件");
    }

    public async ValueTask<OperationalResult<bool>> RemoveFileAsync(string fileid)
    {
        var result = new OperationalResult<bool>();

        var fs = GetProvider();

        var entity = await _context.Set<FileInfo>().FirstOrDefaultAsync(n => n.FileId == fileid);

        if (entity is not null)
        {
            result.Data = await fs.RemoveFileAsync(_options.GroupName, fileid, _options.ClusterName);

            return result;
        }

        result.Status = OperationalResult.FAILURE;
        result.Message = "文件不存在";

        return result;
    }

    public async ValueTask<OperationalResult<string>> UploadFileAsync(UploadModel model, CancellationToken cancellationToken = default)
    {
        var result = new OperationalResult<string>();

        var fs = GetProvider();

        var fileId = string.Empty;

        await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
        try
        {
            var hash = await ComputeHashAsync(model.Buffer, cancellationToken);

            var exists = await _context.Set<FileInfo>().Where(n => n.Hash == hash).Select(n => new { n.Id, n.Url }).FirstOrDefaultAsync(cancellationToken);
            if (exists is not null)
            {
                result.Data = exists.Url;

                return result;
            }

            var fileSize = model.Buffer.Length;

            var watch = new Stopwatch();
            watch.Start();
            fileId = await fs.UploadFileAsync(model);
            watch.Stop();

            Console.WriteLine($"推送至文件服务器耗时：{watch.ElapsedMilliseconds} ms");

            var groupInfo = await fs.ListOneGroupInfoAsync(_options.GroupName, _options.ClusterName);
            var storageNode = await fs.GetStorageNodeAsync(_options.GroupName, fileId, _options.ClusterName);
            storageNode.ConnectionAddress.IPAddress = _options.IPAddress;

            var addedBy = await _context.Set<User>().FindAsync(new object[] { model.AddedBy }, cancellationToken);

            var fileinfo = new FileInfo
            {
                FileId = fileId,
                Url = $"http://{storageNode.ConnectionAddress.IPAddress}:{groupInfo.StorageHttpPort}/{_options.GroupName}/{fileId}",
                GroupName = _options.GroupName,
                Name = model.Name,
                Extension = model.Extension,
                Size = fileSize,
                AddedBy = addedBy!,
                Hash = hash
            };

            await _context.Set<FileInfo>().AddAsync(fileinfo, cancellationToken);

            await _context.SaveChangesAsync(cancellationToken);

            await transaction.CommitAsync(cancellationToken);

            result.Data = fileinfo.Url;

            return result;
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync(cancellationToken);
            await fs.RemoveFileAsync(_options.GroupName, fileId, _options.ClusterName);

            result.Status = OperationalResult.FAILURE;
            result.Message = ex.Message;

            return result;
        }
    }

    public async ValueTask<OperationalResult<IReadOnlyCollection<string>>> UploadFileRangeAsync(IEnumerable<UploadModel> models, CancellationToken cancellationToken = default)
    {
        var result = new OperationalResult<IReadOnlyCollection<string>>();
        var fs = GetProvider();

        var fileId = string.Empty;
        await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);

        var entities = new List<FileInfo>();
        var existsEntities = new List<FileInfo>();

        try
        {
            foreach (var model in models)
            {
                var hash = string.Empty;
                hash = string.IsNullOrEmpty(model.Hash) ? await ComputeHashAsync(model.Buffer, cancellationToken) : model.Hash;

                var exists = _context.Set<FileInfo>().Where(n => n.Hash == hash).Select(n => new { n.Id, n.Url }).FirstOrDefault();
                if (exists is not null)
                {
                    existsEntities.Add(new FileInfo { Id = exists.Id, Url = exists.Url });

                    continue;
                }

                var fileSize = model.Buffer.Length;

                var watch = new Stopwatch();
                watch.Start();
                fileId = await fs.UploadFileAsync(model);
                watch.Stop();

                Console.WriteLine($"推送至文件服务器耗时：{watch.ElapsedMilliseconds} ms");

                var groupInfo = await fs.ListOneGroupInfoAsync(_options.GroupName, _options.ClusterName);
                var storageNode = await fs.GetStorageNodeAsync(_options.GroupName, fileId, _options.ClusterName);
                storageNode.ConnectionAddress.IPAddress = _options.IPAddress;

                var addedBy = await _context.Set<User>().FindAsync(new object[] { model.AddedBy }, cancellationToken);

                var attachment = new FileInfo
                {
                    FileId = fileId,
                    Url = $"http://{storageNode.ConnectionAddress.IPAddress}:{groupInfo.StorageHttpPort}/{_options.GroupName}/{fileId}",
                    GroupName = _options.GroupName,
                    Name = model.Name,
                    Extension = model.Extension,
                    Size = fileSize,
                    AddedBy = addedBy!,
                    Hash = hash
                };

                entities.Add(attachment);
            }

            await _context.Set<FileInfo>().AddRangeAsync(entities, cancellationToken);

            await _context.SaveChangesAsync(cancellationToken);

            await transaction.CommitAsync(cancellationToken);

            entities.AddRange(existsEntities);

            result.Data = entities.Select(x => x.Url).ToList().AsReadOnly();

            return result;
        }
        catch (Exception ex)
        {
            await transaction.RollbackAsync(cancellationToken);

            foreach (var item in entities)
            {
                await fs.RemoveFileAsync(_options.GroupName, item.FileId, _options.ClusterName);
            }

            result.Status = OperationalResult.FAILURE;
            result.Message = ex.Message;

            return result;
        }
    }

    private IFileStorageProvider GetProvider()
    {
        return _provider.GetRequiredService<IFileStorageProvider>();
    }

    private async Task<string> ComputeHashAsync(Stream buffer, CancellationToken cancellationToken = default)
    {
        var watch = new Stopwatch();
        watch.Start();

        await _xxh64.AppendAsync(buffer, cancellationToken);

        var hash = Convert.ToBase64String(_xxh64.GetHashAndReset());

        watch.Stop();

        buffer.Position = 0;

        Console.WriteLine($"哈希计算耗时：{watch.ElapsedMilliseconds} ms");

        return hash;
    }
}